package com.elitesland.org.service.impl;

import cn.hutool.core.util.StrUtil;
import com.elitesland.core.base.PagingVO;
import com.elitesland.org.convert.OrgOuConvert;
import com.elitesland.org.entity.OrgOuDO;
import com.elitesland.org.entity.QOrgOuDO;
import com.elitesland.org.param.OrgOuQParam;
import com.elitesland.org.param.OrgOuSaveParam;
import com.elitesland.org.repo.OrgOuRepo;
import com.elitesland.org.service.OrgOuService;
import com.elitesland.org.vo.OrgOuVO;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class OrgOuServiceImpl implements OrgOuService {

    private OrgOuRepo orgOuRepo;

    @Autowired
    public void setOrgOuRepo(OrgOuRepo orgOuRepo) {
        this.orgOuRepo = orgOuRepo;
    }

    private final QOrgOuDO ouTable = QOrgOuDO.orgOuDO;

    @Override
    public PagingVO<OrgOuVO> searchByPId(Long pId, OrgOuQParam param) {
        val parentId = pId != null && pId >= 0 ? pId : 0L;

        Predicate predicate = ouTable.isNull().or(ouTable.isNotNull());

        predicate = StringUtils.isBlank(param.getCode()) ? predicate :
                ExpressionUtils.and(predicate, ouTable.code.like("%" + param.getCode().trim() + "%"));

        if (StrUtil.isNotBlank(param.getName())) {
            val nameLike = "%" + param.getName().trim() + "%";
            predicate = ExpressionUtils.and(predicate, ouTable.name.like(nameLike).or(ouTable.name.like(nameLike)));
        }
        predicate = StringUtils.isBlank(param.getAbbr()) ? predicate :
                ExpressionUtils.and(predicate, ouTable.abbr.eq(param.getAbbr()));
        predicate = StringUtils.isBlank(param.getAbbrAlpha()) ? predicate :
                ExpressionUtils.and(predicate, ouTable.abbrAlpha.eq(param.getAbbrAlpha()));

        val ouOpt = orgOuRepo.findById(parentId);
        if (ouOpt.isPresent()) {
            predicate = ExpressionUtils.and(predicate, ouTable.path.like(ouOpt.get().getPath() + "%"));
        }

        val ret = orgOuRepo.findAll(predicate, param.getPageRequest());

        return PagingVO.<OrgOuVO>builder()
                .total(ret.getTotalElements())
                .records(ret.getContent().stream().map(OrgOuConvert.INSTANCE::doToVO)
                        .collect(Collectors.toList()))
                .build();
    }

    @Override
    public List<OrgOuVO> listTreeByPId(Long pId) {
        var rootPath = pId == null || pId <= 0 ? "/" : "";
        if (StringUtils.isBlank(rootPath)) {
            rootPath = oneById(pId).map(OrgOuVO::getPath).orElse("/");
        }
        val entities = orgOuRepo.findAllByPathLikeOrderByCode(rootPath + "%")
                .stream().map(OrgOuConvert.INSTANCE::doToVO)
                .collect(Collectors.toList());

        return buildTree(pId, entities);
    }

    @Override
    public List<OrgOuVO> listByIds(List<Long> ids) {
        return orgOuRepo.findAllByIdIn(ids).stream().map(OrgOuConvert.INSTANCE::doToVO)
                .collect(Collectors.toList());
    }

    private List<OrgOuVO> buildTree(Long pId, List<OrgOuVO> entities) {
        val subEntities = new ArrayList<OrgOuVO>();

        entities.stream().filter(e -> e.getParentId().equals(pId))
                .sorted(Comparator.comparing(OrgOuVO::getSortNo)).forEach(e -> {
            val subEs = buildTree(e.getId(), entities);

            e.setChildren(subEs);

            subEntities.add(e);
        });

        return subEntities;
    }

    @Override
    public Optional<OrgOuVO> oneById(Long id) {
        return orgOuRepo.findById(id).map(OrgOuConvert.INSTANCE::doToVO)
                .map(ou -> {
                    val parentOu = oneById(ou.getParentId());
                    ou.setParentOu(parentOu.orElse(null));
                    return ou;
                });
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public Long create(OrgOuSaveParam entity) {
        val saveDO = OrgOuConvert.INSTANCE.saveToDO(entity);

        saveDO.setPath("/");
        oneById(entity.getParentId()).ifPresent(pe -> {
            saveDO.setPath(pe.getPath() + pe.getId() + "/");
        });

        val oldOus = orgOuRepo.findAllByParentId(entity.getParentId())
                .stream().sorted(Comparator.comparing(OrgOuDO::getSortNo))
                .collect(Collectors.toList());

        var index = 1;
        for (OrgOuDO ou : oldOus) {
            ou.setSortNo(index);
            index++;
        }

        orgOuRepo.saveAll(oldOus);

        saveDO.setSortNo(index);

        val newEntity = orgOuRepo.save(saveDO);
        return newEntity.getId();
    }

    @Override
    public void updateSort(List<OrgOuVO> ouVOS) {
        val ids = ouVOS.stream().map(OrgOuVO::getId).collect(Collectors.toList());

        val oldEntities = orgOuRepo.findAllByIdIn(ids);

        val newEntities = new ArrayList<OrgOuDO>();

        var ref = new Object() {
            int index = 1;
        };

        for (OrgOuVO ouVO : ouVOS) {
            oldEntities.stream().filter(oe -> oe.getId().equals(ouVO.getId())).findFirst().ifPresent(e -> {
                e.setSortNo(ref.index);
                newEntities.add(e);

                ref.index++;
            });
        }
        orgOuRepo.saveAll(newEntities);
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public void update(OrgOuSaveParam ouSave) {
        oneById(ouSave.getId()).ifPresent(e -> {
            e.setCode(ouSave.getCode())
                    .setName(ouSave.getName())
                    .setName2(ouSave.getName2())
                    .setName3(ouSave.getName3())
                    .setName4(ouSave.getName4())
                    .setAbbr(ouSave.getAbbr())
                    .setAbbrAlpha(ouSave.getAbbrAlpha())
                    .setType(ouSave.getType())
                    .setType2(ouSave.getType2())
                    .setType3(ouSave.getType3())
                    .setStatus(ouSave.getStatus())
                    .setStatus2(ouSave.getStatus2())
                    .setStatus3(ouSave.getStatus3())
                    .setBuId(ouSave.getBuId())
                    .setBuId2(ouSave.getBuId2())
                    .setBuId3(ouSave.getBuId3())
                    .setBuId4(ouSave.getBuId4())
                    .setParentId(ouSave.getParentId())
                    .setSortNo(ouSave.getSortNo())
                    .setPath(ouSave.getPath())
                    .setStdCurr(ouSave.getStdCurr())
                    .setFinCalCode(ouSave.getFinCalCode())
                    .setAddrNo(ouSave.getAddrNo())
                    .setCountry(ouSave.getCountry())
                    .setProvince(ouSave.getProvince())
                    .setCity(ouSave.getCity())
                    .setRegion(ouSave.getRegion())
                    .setTaxpayerType(ouSave.getTaxpayerType())
                    .setBizCodeCert(ouSave.getBizCodeCert())
                    .setTaxerCode(ouSave.getTaxerCode())
                    .setIcRegisterNo(ouSave.getIcRegisterNo())
                    .setTaxRegNo(ouSave.getTaxRegNo())
                    .setUniCertNo(ouSave.getUniCertNo())
                    .setExternalCode(ouSave.getExternalCode())
                    .setEs1(ouSave.getEs1())
                    .setEs2(ouSave.getEs2())
                    .setEs3(ouSave.getEs3())
                    .setEs4(ouSave.getEs4())
                    .setEs5(ouSave.getEs5())
                    .setEs6(ouSave.getEs6())
                    .setEs7(ouSave.getEs7())
                    .setEs8(ouSave.getEs8())
                    .setEs9(ouSave.getEs9())
                    .setEs10(ouSave.getEs10());

            orgOuRepo.save(OrgOuConvert.INSTANCE.voToDO(e));
        });
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public void removeById(Long id) {
        oneById(id).ifPresent(e -> {
            orgOuRepo.deleteAllByPathLike(e.getPath() + e.getId() + "/%");
        });
    }
}
